//Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using Eas.WebCrawler.Model.Core;
using Eas.WebCrawler.Interfaces;

namespace Eas.WebCrawler.Model.Actions {
    internal class ReadStreamAction : ProcessingAction{

        static ReadStreamAction() {
            Array.Sort<string>( InvalidSchemes);
        }

        private static string[] InvalidSchemes = new string[] { "mailto", "javascript" };
        

        #region ProcessingAction Members

        public void PerformCompletingAction(WorkerContext context, Eas.WebCrawler.Model.Core.BaseProcessingState state, IAsyncResult result) {
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Calling StreamRead::PerformCompletingAction.");
            if (!state.StreamReadHasCompleted) {
                context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Calling EndRead As Completing Action.");
                context.StateTracker.CloseResult(result);
                try {
                    context.WebGateway.EndReadStream(state, result);
                }
                catch (System.Exception) { }
            }
            else {
                context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Not calling EndRead because read completed synchronously");
            }
        }

        public IAsyncResult BeginAction(WorkerContext context, Eas.WebCrawler.Model.Core.BaseProcessingState state, AsyncCallback callback) {
            if (state.ResponseStore == null) {
                System.Diagnostics.Debug.Assert(state.StreamReadHasCompleted == false);
                state.ResponseStore = context.WebCrawlerContext.StorageFactory.Create(state);
                if (state.IsTextResponse) {
                    state.StorageWriter = new Eas.WebCrawler.Model.StorageWriters.TextStorageWriter();
                }
                else {
                    state.StorageWriter = new Eas.WebCrawler.Model.StorageWriters.BinaryStorageWriter();
                }
                state.StorageWriter.Initialize(state.ResponseStore);
            }
            System.Diagnostics.Debug.Assert(state.StreamReadHasCompleted == false);
            IAsyncResult result = null;
            try {

                while (result == null || (result.IsCompleted && (!state.StreamReadHasCompleted))) {
                    context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Calling BeginRead: ResultIsNull: {0}\tResultIsComplete: {1}\tstate.StreamReadHasCompleted:{2}", result == null, (result == null ? false : result.IsCompleted), state.StreamReadHasCompleted);
                    
                    result = context.WebGateway.BeginReadStream(state, state.ResponseBuffer, 0, state.ResponseBuffer.Length, callback, state);
                    context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Returned from BeginRead");
                    context.StateTracker.OpenResult(result, this);

                    if (result.IsCompleted) {
                        context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Read completed synchronously, calling EndRead");
                        DoEndAction(context, state, result, false);
                    }
                }
            }
            catch (System.Net.WebException exception) {
                throw new WebFailureException("Failed to get response stream from server: " + exception.Message, context.WebGateway.GetResponseCode(exception), exception);
            }
            catch (System.Exception exception) {
                throw new WebFailureException("Failed to get response stream from server: " + exception.Message, 500, exception);
            }

            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Returning from BeginRead");
            return result;
        }

        public ProcessingAction EndAction(WorkerContext context, Eas.WebCrawler.Model.Core.BaseProcessingState state, IAsyncResult result) {
            return DoEndAction(context, state, result, true);
        }

        private ProcessingAction DoEndAction(WorkerContext context, Eas.WebCrawler.Model.Core.BaseProcessingState state, IAsyncResult result, bool resetState) {
            if (state.StreamReadHasCompleted) {
                context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "state.StreamReadHasCompleted == true, returning.");
                if (resetState)
                    state.Reset();
                return context.ActionFactory.CreateGetRequestAction(state, context);
            }

            int readBytes;
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Calling EndRead");
            context.StateTracker.CloseResult(result);

            try {

                readBytes = context.WebGateway.EndReadStream(state, result);
            }
            catch (System.Net.WebException exception) {
                throw new WebFailureException("Failed to get response stream from server: " + exception.Message, context.WebGateway.GetResponseCode(exception), exception);
            }
            catch (System.Exception exception) {
                throw new WebFailureException("Failed to get response stream from server: " + exception.Message, 500, exception);
            }

            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Read {0} bytes", readBytes);
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Processing Read Bytes");

            return ProcessReadBytes(state, context, readBytes, resetState);
        }

        private static void ProcessResults(BaseProcessingState state, WorkerContext context) {
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Read completed, setting StreamReadHasCompleted = true.");
            state.StreamReadHasCompleted = true;

            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Closing writer.");
            state.StorageWriter.Dispose();
            state.StorageWriter = null;
            
            LogSuccessfullCompletionInHistory(state, context);
            NotifyUriProcessed(state, context);
            if (state.IsTextResponse)
                ProcessInternalLinks(state, context);
            System.Threading.Thread.Sleep(0); //Just completed a request, yield CPU to others.
        }

        private static void ProcessInternalLinks(BaseProcessingState state, WorkerContext context) {
            Eas.WebCrawler.Interfaces.LinkExtractor extractor = context.WebCrawlerContext.LinkExtractor;
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Extracting Links");
            foreach (Uri uri in extractor.GetLinks(state, state.ResponseStore.GetTextReader())) {
                Eas.WebCrawler.Interfaces.Request request;
                try {
                    request = new Eas.WebCrawler.Interfaces.Request(uri, state.Request);
                }
                catch (System.Exception) {
                    context.EventReporter.ReportTrace(TraceLevel.HighImpact, state, "Invalid URI '{0}' encountered while parsing response for request {1}.  Continuing to next URI...", uri, state.Request);
                    continue;
                }

                if (Array.BinarySearch<string>(InvalidSchemes, request.Uri.Scheme) >= 0) {
                    context.EventReporter.ReportTrace(TraceLevel.Detailed, state, "Request '{0}' found but not added because its scheme is 'invalid'", request.Uri.OriginalString);
                    continue;
                }

                try {
                    if (!context.WebCrawlerContext.Selector.ShouldFollow(request)) {
                        context.EventReporter.ReportTrace(TraceLevel.Detailed, state, "Request '{0}' found but not added because selector returned false", request.Uri);
                        continue;
                    }
                }
                catch (System.Exception) {
                    context.EventReporter.ReportTrace(TraceLevel.Detailed, state, "Request '{0}' found but not added because selector threw an exception", request.Uri);
                    throw;
                }

                if (context.WebCrawlerContext.Store.AddRequest(request))
                    context.EventReporter.ReportTrace(TraceLevel.Detailed, state, "Request '{0}' added to store.", request.Uri);
                else
                    context.EventReporter.ReportTrace(TraceLevel.Detailed, state, "Request '{0}' found but not add because it is already in store (or uri is empty)", request.Uri);
            }
        }

        private static void NotifyUriProcessed(BaseProcessingState state, WorkerContext context) {
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Firing completion event.");
            context.EventReporter.ReportRequestComplete(state, state.ResponseStore);
        }

        private static void LogSuccessfullCompletionInHistory(BaseProcessingState state, WorkerContext context) {
            context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "Logging Successful Completion in history.");
            context.WebCrawlerContext.CompletedRequestHistory.AddCompletedRequest(state.Request.Uri);
        }

        private ProcessingAction ProcessReadBytes(BaseProcessingState state, WorkerContext context, int readBytes, bool resetState) {
            if (readBytes > 0){
                context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "ReadBytes != 0, adding response to StorageWriter and continuing to read.");
                state.StorageWriter.Add(state.ResponseBuffer, readBytes);
                return this;
            }
            else{
                context.EventReporter.ReportTrace(TraceLevel.Annoying, state, "ReadBytes == 0, finishing request processing.");
                ProcessResults(state, context);
                if (resetState)
                    state.Reset();
                return context.ActionFactory.CreateGetRequestAction(state, context);
            }
        }

        public Eas.WebCrawler.Interfaces.WorkerState WorkerState {
            get { return Eas.WebCrawler.Interfaces.WorkerState.Working; }
        }

        public ActionId ActionId {
            get { return ActionId.ReadStream; }
        }

        #endregion
    }
}
